﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Data.SqlClient;
using MetaCollection.Framework.Helpers;
using System.IO;
using System.Data;
using MetaCollection.Framework.Model;

namespace MetaCollection.Framework.Repository
{
	public class AdoNetResource : IResourceRepository
	{
		private const int BUFFER_LENGTH = 16384;
		private string m_ConnectionString;
		private bool m_Initialized;

		/// <summary>
		/// Ensures the repository has been initialized.
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if <see cref="ResourceProvider.Initialize"/> has not been called.</exception>
		private void EnsureInitialized()
		{
			if (!m_Initialized)
				throw new InvalidOperationException("Resource Provider must be initialized prior to use.");
		}

		#region IResourceRepository Members

		/// <summary>
		/// Initializes this adonet resource repository by using the configdata as a connection string..
		/// </summary>
		/// <param name="configData">The config data (which is a connection string).</param>
		public void Initialize(string configData)
		{
			m_ConnectionString = configData;
			m_Initialized = true;
		}

		/// <summary>
		/// Gets the resource stream for the given item.
		/// </summary>
		/// <param name="item">The item to get the resource stream for</param>
		/// <returns>
		/// A stream to read the item's resource from
		/// </returns>
		public System.IO.Stream GetResourceStream(MetaCollection.Framework.Model.IItemInstance item)
		{
			if (item == null)
				throw new ArgumentNullException("item", "Item can not be null.");

			EnsureInitialized();

			using (SqlCommand readData = new SqlCommand("SELECT Resource FROM structure.ItemResource WHERE ItemId = @id and Version = @version", new SqlConnection(m_ConnectionString)))
			{
				readData.Parameters.AddWithValue("@id", item.Id);
				readData.Parameters.AddWithValue("@version", item.Version);
				return new SqlDataReaderStream(readData, 0); // our implementation of this will correctly handle closing and disposing our connection.
			}
		}

		/// <summary>
		/// Saves the resource stream provided for the given item.
		/// </summary>
		/// <param name="item">The item associated with this resource stream.</param>
		/// <param name="data">The data to save as the resource for this item.</param>
		public void SaveResourceStream(IItemInstance item, Stream data)
		{
			if (item == null)
				throw new ArgumentNullException("item", "Item can not be null.");

			if (data == null)
				throw new ArgumentNullException("data", "Data stream can not be null.");

			EnsureInitialized();
			using (SqlCommand saveStream = new SqlCommand("DELETE FROM structure.ItemResource WHERE ItemId=@ItemId and Version=@Version;INSERT INTO structure.ItemResource (ItemId, Version, Resource) VALUES (@ItemId, @Version, 0x0);UPDATE structure.ItemVersion SET LastModified=getDate() WHERE ItemId=@ItemId and Version=@Version;", new SqlConnection(m_ConnectionString)))			
			{
				saveStream.Parameters.AddWithValue("@ItemId", item.Id);
				saveStream.Parameters.AddWithValue("@Version", item.Version);
				saveStream.Parameters.AddWithValue("@RowId", Guid.NewGuid());
				saveStream.Parameters.AddWithValue("@FileExt", Path.GetExtension(item.Name));
				saveStream.Connection.Open();
				saveStream.ExecuteNonQuery();
				StoreStream(data, item, saveStream.Connection);
				if (saveStream.Connection.State != ConnectionState.Closed)
					saveStream.Connection.Close();
			}
		}

		#endregion

		/// <summary>
		/// Stores the stream in the database
		/// </summary>
		/// <param name="data">The data to store.</param>
		/// <param name="pointer">A pointer to the column in the database to store the data in.</param>
		/// <param name="connection">The connection.</param>
		private static void StoreStream(Stream data, IItemInstance item, SqlConnection connection)
		{
			using (SqlCommand saveResource = new SqlCommand("UPDATE structure.ItemResource SET Resource.WRITE(@Bytes, @Offset, @Length) WHERE ItemId=@ItemId and Version=@Version", connection))
			{
				saveResource.Parameters.AddWithValue("@ItemId", item.Id);
				saveResource.Parameters.AddWithValue("@Version", item.Version);

				SqlParameter dataLength = saveResource.Parameters.Add("@Length", SqlDbType.Int);
				SqlParameter dataBuffer = saveResource.Parameters.Add("@Bytes", SqlDbType.Binary, BUFFER_LENGTH);
				SqlParameter dataOffset = saveResource.Parameters.Add("@Offset", SqlDbType.BigInt);

				// Read the image in and write it to the database 128 (bufferLen) bytes at a time.
				using (BinaryReader br = new BinaryReader(data))
				{
					long offset = br.BaseStream.Position; // by starting at the base stream position we can update a portion of the binary (useful byte range replacement for example) in most cases this will be zero.
					dataOffset.Value = offset;

					byte[] buffer = br.ReadBytes(BUFFER_LENGTH);
					dataLength.Value = buffer.Length;

					while (buffer.Length > 0)
					{
						dataBuffer.Value = buffer;
						saveResource.ExecuteNonQuery();
						offset += buffer.Length;
						dataOffset.Value = offset;
						buffer = br.ReadBytes(BUFFER_LENGTH);
						dataLength.Value = buffer.Length;
					}
					br.Close();
					data.Close();
				}
			}
		}
	}
}
